Fork me on GitHub

SPRING AOP 续二

注意:所有文章除特别说明外,转载请注明出处.

1 概述

1.2 AOP术语

...

引介:是一种特殊的增强,它为类添加一些属性和方法,由此即使一个业务类原本没有实现接口,通过AOP的引介功能可以动态的为业务类添加接口的实现逻辑。

织入:将增添添加到目标类的具体连接点上的过程。

代理:类被AOP织入增强之后,就产生了一个结果类,它是融合了原类和增强逻辑的代理类。根据不同的代理方式,代理类可能是和原类具有相同接口的类,或者是是原类的子类。

重点:AOP的重点在于如何将增强应用于目标对象的连接点上。1.如何通过切点和增强定位到连接点上。2.如何在增强中编写切面的程序。

1.3 AOP实现者

1.AspectJ 扩展Java语言,定义AOP语法,能够在编译取期提供横切程序织入,它有一个专门的编译器用来生成遵守Java字节编码规范的Class文件。

2.Spring AOP 使用纯Java实现,不需要专门的编译过程。其侧重于提供一种和Spring IoC容器整合的AOP实现。

2 基础知识

Spring AOP使用动态代理技术在运行期织入增强的程序。Spring有两种代理机制:1.基于JDK动态代理技术。2.基于CGLib动态代理技术。

2.2 JDK动态代理技术

JDK动态代理主要涉及:java.lang.reflect包中的两个类:Proxy | InvocationHandler。其中InvocationHandler是一个接口,通过实现该接口定义横切逻辑,同时通过反射机制调用目标类的程序,动态地将横切逻辑和业务逻辑编织在一起。Proxy是利用InvocationHandler动态创建一个符合某一接口的实例,生成目标类的代理对象。

//这里还有一个目标实现类,将其中的横切代码抽取出来
//..这里不写
public class ForumServiceImpl implements ForumService{
    ...
}

//...import
public class PerformanceHandler implements InvocationHandler {

    //1.target是目标业务类
    private Object target;
    public PerformanceHandler(Object target){
        this.target = target;
    }

    //该方法将横切逻辑程序和业务类方法程序编织到一起
    //proxy:表示最终生成的代理实例,一般不会用到
    //method:表示被代理目标实例的某个具体方法,通过发起目标实例方法的反射调用
    //args:被代理实例某个方法的入参
    public Object invoke(Object proxy, Method method, Object[] args) throws  Throwable {
        //1.1 横切逻辑实现
        PerformanceMonitor.begin(target.getClass().getName() +"." + method.getName());

        //2.通过反射方法调用目标业务类的反射方法
        Object obj = method.invoke(target, args);

        PerformanceMonitor.end();

        return obj;
    }
}

//@Test
public class ForumServiceTest {
    @Test
    public void proxy() {
        //1.希望被代理的目标业务类
        ForumService target = new ForumServiceImpl();

        //2.将目标业务类和横切程序编织在一起
        PerformanceHandler handler = new PerformanceHandler(target);

        //3.创建代理实例
        ForumService proxy = (ForumService) Proxy.newProxyInstance(
            target.getClass().getClassLoader(),
            target.getClass().getInterfaces(),
            handler);

        //4.执行调用代理实例
        proxy.removeForum(10);
        proxy.removeTopic(1012);
    }
}

2.3 CGLib动态代理

使用JDK动态代理有限制,其只能为接口创建代理实例。而对于没有通过接口定义业务方法的类可以为其创建子类,在子类中采用方法拦截,拦截所有父类方法的调用并顺势织入横切逻辑。

//这里还有一个目标实现类,将其中的横切代码抽取出来
//..这里不写
public class ForumServiceImpl implements ForumService{
    ...
}

//..import
public class CglibProxy implements MethodInterceptor {
    private Enhancer enhancer = new Enhancer();
    public Object getProxy(Class clazz) {

        //1.设置创建子类的类
        enhancer.setSuperclass(clazz);
        enhancer.setCallback(this);

        //2.通过字节码技术动态创建子类实例
        return enhancer.create();
    }

    //3.拦截父类所有方法调用
    public Object intercept (Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {

        //织入横切逻辑程序
        PerformanceMonitor.begin(obj.getClass().getName() + "." + method.getName());

        //3.通过代理类调用父类中的方法
        Object result = proxy.invokeSuper(obj, args);
        PerformanceMonitor.end();
        return result;
    }
}


//@Test
//...import
public class ForumServiceTest {
    @Test
    public void proxy() {
        CglibProxy proxy = new CglibProxy();

        //动态生成子类创建代理类
        ForumServiceImpl forumServiceImpl = (ForumServiceImpl) proxy.getProxy(ForumServiceImpl.class);

        forumServiceImpl.removeForum(10);
        forumServiceImpl.removeTopic(1023);
    }
}

3 创建增强类

3.1 前置增强

BeforeAdvice是前置增强的接口,MethodBeforeAdvice接口是其子类,该接口只定义唯一一个方法:before(Method method, Object[] args, Object obj) throws Throwable{}。

//省略before的实现过程..

//@Test
...
//1.Spring提供代理工厂
ProxyFactory pf = new ProxyFactory();

//2.设置目标类
pf.setTarget(target);

//3.为代理目标增强
pf.addAdvice(advice);

//4.生成代理实例
Waiter proxy = (Waiter)pf.getProxy();
proxy.greetTo("John");
proxy.serveTo("Tom");

3.1.1 ProxyFactory解析

3.1.2 在Spring中配置

<bean id="greetingAdvice" class="com.smart.advice.GreetingBeforeAdvice"/>
<bean id="target" class="com.smart.advice.NaiveWaiter"/>
<bean id="waiter" class="org.springframework.aop.frmework.ProxyFactoryBean"
    //1.指定代理的接口,如果是多个使用<list>元素
    p:proxyInterfaces="com.smart.advice.Waiter"

    //2.指定使用的增强
    p:interceptorNames="greetingAdvice"

    //3.指定对哪个bean进行代理
    p:target-ref="target"
/>

3.2 前置增强

3.3 前置增强

3.4 前置增强


4 创建切面

增强提供了连接点方位信息,如织入到方法前面、后面等,而切点进一步描述了织入哪些类的哪些方法上。Spring通过org.springframework.aop.Pointcut接口描述切点,Pointcut由ClassFilter和MethodMatcher构成,其通过ClassFilter定位到某些特定类,然后通过MethodMatcher定位到某些特定方法。这样Pointcut就拥有了描述某些类的某些特定方法能力。

1.ClassFilter方法 matches(Class clazz):clazz表示一个被检测类,该方法判断被检测类是否匹配过滤条件。这里匹配器包括两种:静态方法匹配器和动态方法匹配器。

7.4.1 切点类型

1.静态方法切点

2.动态方法切点

3.注解切点

4.表达式切点

该切点主要是为了支持AspectJ切点表达式语法定义的接口。

5.流程切点

6.复合切点

7.4.2 切面类型

1.Advisor 一般切面

一般切面仅包含一个Advice,该切面包含了横切程序和连接点信息。

2.PointcutAdvisor

表示具有切点的切面,包含Advice和Pointcut两个类。从而可以通过类、方法名以及方法方位等信息灵活定义切面的连接点。

3.IntroductionAdvisor

表示引介切面。

PointcutAdvisor主要有六个具体的实现类:

1.DefaultPointAdvisor:最常用的切面类型

2.NameMatchMethodPointcutAdvisor:通过该类可以定义按方法名定义切点的切面

3.RegexpMethodPointcutAdvisor:通过按正则表达式匹配方法名进行切点定义的切面

4.StaticMethodMatcherPointcutAdvisor:静态方法匹配器切点定义的切面。

5.AspectJExceptionPointcutAdvisor:用于AspectJ切点表达式定义切点的切面。

6.AspectJPointcutAdvisor:用于AspectJ语法定义切点的切面。

7.5 自动创建代理

Spring提供自动代理机制,让容器自动生成代理。

7.5.1 BeanNameAutoProxyCreator

该代理器有一个beanNames属性,允许用户指定一组需要自动代理的Bean名称。

7.5.2 DefaultAdvisorAutoProxyCreator

能够扫描容器中的Advisor,并将Advisor自动织入匹配的目标Bean中。


第8章 基于@AspectJ和Scheme的AOP

8.2 Java 5.0 注解知识进阶

8.2.2 简单的注解类

//...import
//1.声明注解的保留期限 这里表示可以被JVM读取
@Retention(RetentionPolicy.RUNTIME)
//2.声明可以使用该注解的目标类型
@Target(ElementType.METHOD)
//3.定义注解
public @interface NeedTest{
    //4.声明注解成员
    boolean value() default true;
}

提示:成员变量的声明有一些限制:1.成员以无入参、无抛出异常的方式声明。2.可以通过default为成员指定一个默认值。3.成员类型是受限的,合法的类型包括原始类型和封装类型。

提示:注解的保留期限:1.SOURCE:注解信息仅保留在目标类代码的源码文件中,对应的字节码文件将不再保留。2.CLASS:注解信息将进入目标类代码的字节码文件中,但类加载器加载字节码文件时将注解加载到JVM中。3.RUNTIME:注解信息在目标类加载到JVM后依然保留,在运行期间通过反射机制读取类中的注解信息。

8.2.3 使用注解

public class ForumService {
    @NeedTest(value = true)
    public void deleteForum(int forumId){
        System.out.println("删除论坛模块:" + forumId);
    }

    @NeedTest(value = false)
    public void deleteTopic(int postId){
        System.out.println("删除论坛主题:" + postId);
    }

8.2.4 访问注解

对于RetentionPolicy.RUNTIME保留期限的注解,可以通过反射机制访问类中的注解。

public class ToolTest {
    @Test
    public void tool(){
        //1.得到ForumService对应的Class对象
        Class clazz = ForumService.class;

        //2.得到ForumService对应的Method数组
        Method[] methods = clazz.getDeclaredMethod();

        System.out.println(methods.length);
        for(Method method : methods){
            //3.获取方法上所标注的注解对象
            NeedTest nt = method.getAnnotation(NeedTest.class);
            if(nt != null){
                if(nt.value()){
                    System.out.println(method.getName()+"()需要测试");
                }else{
                    System.out.println(method.getName() + "()不需要测试");
                }
            }
        }
    }
}

8.3 使用@AspectJ

8.3.1 准备 - 案例

在pom.xml中添加aspectj.weaver和aspectj.tools类包的依赖。

public class NaiveWaiter implements Waiter {
    public void greetTo(String clientName){
        System.out.println("greet to" + clientName + "...");
    }

    public void serveTo(String clientName){
        System.out.println("serving" + clientName + "...");
    }
}

//使用@AspectJ注解定义切面
@AspectJ
public class PreGreetingAspect {
    //1.定义切点和增强类型
    @Before("execution(* greetTo(...))")
    public void beforeGreeting(){
        //2.增强的横切逻辑
        System.out.println("how are you");
    }
}

//通过AspectJProxyFactory为NavieWaiter生成织入PreGreetingAspect切面的代理
public class AspectJProxyTest{
    @Test
    public void proxy(){
        Waiter target = new NavieWaiter();
        AspectJProxyFactory factory = new AspectJProxyFactory();

        //1.设置目标对象
        factory.setTarget(target);

        //2.添加切面类,该类必须带有@AspectJ的注解
        factory.addAspect(PreGreetingAspect.class);

        //3.生成织入切面的代理对象
        Waiter proxy = factory.getProxy();

        proxy.greetTo("John");
        proxy.serveTo("John");
    }
}

8.3.3 通过配置使用@AspectJ切面

虽然可以通过编程的方式织入切面,但是一般通过Spring的配置完成切面的织入。

<!--1.目标bean-->
<bean id="waiter" class="com.smart.NaiveWaiter"/>

<!--2.使用@AspectJ注解的切面类-->
<bean class="com.smart.aspectj.example.PreGreetingAspect"/>

<!--3.自动代理创建器,自动将@AspectJ注解切面类织入目标bean中-->
<bean class="org.springframework.aop.aspectj.annotation.AnnotationAwareAspectJAutoProxyGreator"/>

提示:自动代理创建器:AnnotationAwareAspectJAutoProxyCreator能够将@AspectJ注解切面类自动织入目标bean中。

1.使用Schema的aop命名空间进行配置

<?xml version="1.0" encoding="utf-8"?>
<beans xmlns=""
....
>

    //3.基于@AspectJ切面的驱动器
    <aop:aspectj-autoproxy/>
    <bean id="waiter" class="com.smart.NaiveWaiter"/>
    <bean class="com.smart.aspectj.example.PreGreetingAspect"/>
</beans>

这里首先在配置文件中引入aop命名空间,然后通过aop命名空间的 aop:apectj-autoproxy/ 自动给Spring容器中那些匹配@AspectJ 切面的Bean创建代理,完成切面织入。

提示:1.配置文件引入aop命名空间。2.然后通过aop命名空间的aop:aspectj-autoproxy/自动给Spring容器中匹配@AspectJ切面的Bean创建代理,完成切面织入。

注意:aop:aspectj-autoproxy/有一个proxy-target-class属性,默认是false,表示使用JDK动态代理技术织入增强。当配置proxy-target-class为true时,表示使用CGLIB动态代理技术织入增强。

8.4 @AspectJ 语法

8.4.1 切点表达式函数

1.execution() 表示目标类执行某一方法
2.匹配串 表示函数的入参

根据描述对象的不同可以将切点表达式分为4中类型:

1.方法切点函数:通过描述目标类方法的信息定义连接点

    1.execution() 方法入参匹配串
    2.@annotation() 方法注解类名

2.方法入参切点函数:通过描述目标类方法入参的信息定义连接点

    1.args() 类名
    2.@args() 类型注解类名

3.目标类切点函数:通过描述目标类类型的信息定义连接点

    1.within() 类型匹配串
    2.target() 类名
    3.@within() 类型注解类名
    4.@target() 类型注解类名

4.代理类切点函数:通过描述目标类的代理类的信息定义连接点

    this() 类名

8.4.2 在函数入参中使用通配符

@AspectJ支持3中通配符:

1.* 匹配任意字符
2... 匹配任意字符
3.+ 表示按类型匹配指定类的所有类

提示:如果not位于切点表达式的开头,则需要在开头添加一个空格,否则将产生解析错误。

8.4.4 不同增强类型

1.@Before 前置增强

2.@AfterReturning 后置增强

3.@Around 环绕增强

4.@AfterThrowing 抛出增强

5.@After Final增强

6.DeclareParents 引介增强

8.4.5 引介增强使用

@Aspect
public class EnableSellerAspect {
    //1.defaultImpl = SmartSeller.class 表示为NavieWaiter添加接口实现
    @DeclareParents(value = "com.smart.NavieWaiter", defaultImpl = SmartSeller.class)

    //2.要实现的目标接口
    public Seller seller;
}

//然后在Spring配置文件配置好切面和NavieWaiter Bean
<aop:aspectj-autoproxy/>
<bean id="waiter" class="com.smart.NavieWaiter"/>
<bean class="com.smart.aspectj.basic.EnableSellerAspect"/>

//测试
public class DeclaredParentsTest {
    public static void main(String[] args){
        String configPath = "com/smart/aspectj/basic/beans.xml";
        ApplicationContext ctx = new ClassPathXmlApplicationContext(configPath);

        Waiter waiter = (Waiter)ctx.getBean( "waiter");
        waiter.greetTo("John");
        Seller seller = (Seller)waiter;
        seller.sell("beer", "john");
    }
}

8.5 切点函数详解

8.5.1 @annotation()

@annotation 注解表示标准某个注解的所有方法。

@Aspect
public class TestAspect {
    //后置增强
    @AfterReturning("@annotation(com.smart.anno.NeedTest)")
    public void needTestFun(){
        System.out.println("needTestFun() ex...");
    }
}

8.5.2 execution()

该函数是常用的切点函数。

execution(<修饰符模式> ? <返回类型模式> <方法名模式> (<参数模式>) <异常模式>?)

1.通过方法签名定义切点

execution(public * *(..)); 匹配目标类中所有public方法

execution(* *(..)); 匹配目标类所有方法

2.通过类定义切点

execution(* com.smart.Waiter.*(..)) 匹配Waiter接口所有方法

execution(* com.smart.Waiter+.*(..)) 匹配Waiter接口及其所有实现类的方法

3.通过类包定义切点

execution(* com.smart.*(..)) 匹配com.smart包下所有类的所有方法

execution(* com.smart..*(..)) 

4.通过方法入参定义切点

execution(* joke(String, int)) 匹配joke(String, int)方法

8.5.3 args() | @args()

1.args()

args()函数接收一个类名,表示目标类方法入参对象是指定类时,切点匹配。

args(com.smart.Waiter) 表示入参是Waiter类型的方法。与excution(* *(com.smart.Waiter))区别在于后者是针对类方法的签名而言,前者是针对运行时的入参类型而言。

2.@args()

该函数只接收一个注解类的类名,当方法得到运行时入参对象标注了指定的注解时,匹配其诶单。

8.5.4 within()

通过匹配模式串声明切点,within()函数定义的连接点是针对目标类而言,不是针对运行期对象的类型而言。

within(<类匹配模式>)

//案例
within(com.smart.*) 匹配com.smart包下面的所有类,但不包括子孙包

8.5.5 @within() | @target()

这两个用于注解的切点函数只接受注解类名作为入参。

@target(M) 匹配任意标注了@M的目标类
@within(M) 匹配标注了@M的类及子孙类

8.5.6 target() | this()

target(M) 表示如果目标类按类型匹配与M,则目标类的所有方法都匹配切点。

this()

8.6 @AspectJ 进阶

8.6.1 切点复合运算

@Aspect
public class TestAspect{
    //与运算
    @After("within(com.smart.*)" + "&& execution(* greetTo(..))")
    public void greeToFun(){
        System.out.println("""...");
    }

    //非与运算
    @Before("!target(com.smart.NavieWaiter)" + "&& execution(* serveTo(..))")
    public void notServeInWaiter(){
        System.out.println("""...");
    }

    //或运算
    @AfterReturning("target(com.smart.Waiter) ||" + " target(com.smart.Seller)")
    public void waiterOrSeller(){
        System.out.println("""...");
    }
}

8.6.2 命名切点

如果希望在其它地方重用一个切点,可以通过@Pointcut注解及切面类方法对切点进行命名。

@Pointcut("execution(* greetTo(..))")
protected void greetTo(){}

//1.@Pointcut 命名切点注解
//2.@Pointcut后面部分是切点表达式
//3.protected 表示切点引用修饰符
//4.greetTo() 表示切点名称

命名切点 使用 类方法作为切点的名称,此外方法的访问修饰符还控制切点的可引用性,这种可引用性和类方法的可访问性相同。命名切点仅利用方法名和访问修饰符的信息,所以习惯方法上返回类型是void类型,方法体为空。


//案例
@Aspect
public class TestAspect {
    //1.引用切点
    @Before("TestNamePointcut.inPkgGreetTo()")
    public void pkgGreetTo(){
        System.out.println("....");
    }

    @Before("!target(com.smart.NavieWaiter) && TestNamePointcut.inPkgGreetTo()")
    public void pkgGreetToNotNavieWaiter(){
        System.out.println("..");
    }
}

8.6.3 增强织入顺序

8.6.4 访问连接点信息

AspectJ使用org.aspectj.lang.JoinPoint 接口表示目标类连接点对象。如果是环绕增强的话则使用 org.aspectj.lang.ProceedingJoinPoint 表示连接点对象,该类是JoinPoint的子接口。

提示:任何增强方法都可以通过将第一个入参声明为 JoinPoint 访问连接点上下文信息。

1.JoinPoint()方法

2.ProceedingJoinPoint()方法

//案例
@Aspect
public class TestAspect {
    //1.环绕增强
    @Around("execution(* greetTo(..)) && target(com.smart.NavieWaiter)")
    //2.声明连接点入参,要在第一位置声明如入参
    public void joinPointAccess(ProceedingJoinPoint pjp) throws Throwable {
        System.out.println("...");
    }

    //3.访问连接点信息
    System.out.println("args[0]:" + pjp.getArgs()[0]);
    System.out.println("signature:" + pjp.getTarget().getClass());

    //4.通过连接点执行目标对象方法
    pjp.proceed();
    System.out.println("...");
} 

8.7 基于Schema配置切面

aop:aspect 元素标签定义切面,在其内部可以定义多个增强。在 aop:config 元素标签中可以定义多个切面。使用 aop:pointcut 定义一个切点,并且通过 id属性进行命名。 通过 pointcut-ref 引用这个命名切点。 如果 aop:pointcut 位于 aop:aspect 元素中,则命名切点只能被当前 aop:aspect 内定义的元素访问到。 所以为了能被整个 aop:config 元素中定义的所有增强访问,必须在 aop:config 元素下定义切点。

<?xml version="1.0" encoding="UTF-8" ?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
       http://www.springframework.org/schema/aop
       http://www.springframework.org/schema/aop/spring-aop-3.0.xsd">

    <!--1.在<aop:config>元素中可以定义多个切面 proxy-target-class="true" :该属性的作用是声明的切面均使用CGLib动态代理技术(true)-->
    <aop:config proxy-target-class="true">

        <!--2.通过<aop:aspect>>元素定义切面 其内部可以定义多个增强-->
        <aop:aspect ref="adviceMethods">
            <aop:before pointcut="target(com.yyq.schema.NaiveWaiter) and execution(* greetTo(..))"
                        method="preGreeting"/>
        </aop:aspect>
    </aop:config>
    <bean id="adviceMethods" class="com.yyq.schema.AdviceMethods"/>
    <bean id="naiveWaiter" class="com.yyq.schema.NaiveWaiter"/>
    <bean id="naughtyWaiter" class="com.yyq.schema.NaughtyWaiter"/>
</beans>

增强方法所在类

package com.baobaotao.schema;
import org.aspectj.lang.ProceedingJoinPoint;

public class AdviceMethods {
    public void preGreeting(String name) {
        System.out.println("--how are you!--");
        System.out.println(name);
    }
    //后置增强对应方法
    public void afterReturning(int retVal){
       System.out.println("----afterReturning()----");
       System.out.println("returnValue:"+retVal);
       System.out.println("----afterReturning()----");
    }
    //环绕增强对应方法
    public void aroundMethod(ProceedingJoinPoint pjp){
       System.out.println("----aroundMethod()----");
       System.out.println("args[0]:"+pjp.getArgs()[0]);
       System.out.println("----aroundMethod()----");
    }
    //抛出异常增强
    public void afterThrowingMethod(IllegalArgumentException iae){
       System.out.println("----afterThrowingMethod()----");
       System.out.println("exception msg:"+iae.getMessage());
       System.out.println("----afterThrowingMethod()----");
    }
    //final增强
    public void afterMethod(){
       System.out.println("----afterMethod()----");
    }

      //------------绑定连接点参数----------//
    public void bindParams(int num,String name){
       System.out.println("----bindParams()----");
       System.out.println("name:"+name);
       System.out.println("num:"+num);
       System.out.println("----bindParams()----");
    }
}

NaiveWaiter类

package com.yyq.schema;
public class NaiveWaiter implements Waiter {
    @Override
    public void greetTo(String name) {
        System.out.println("NaiveWaiter:greet to " + name + "...");
    }
    @Override
    public void serveTo(String name) {
        System.out.println("NaiveWaiter:serving to " + name + "...");
    }
    public void smile(String clientName,int times){
        System.out.println("NaiveWaiter:smile to  "+clientName+ times+"times...");
    }
}

NaughtyWaiter类

package com.yyq.schema;
public class NaughtyWaiter implements Waiter {
    public void greetTo(String clientName) {
        System.out.println("NaughtyWaiter:greet to " + clientName + "...");
    }
    public void serveTo(String clientName) {
        System.out.println("NaughtyWaiter:serving " + clientName + "...");
    }
    public void joke(String clientName, int times) {
        System.out.println("NaughtyWaiter:play " + times + " jokes to " + clientName + "...");
    }
}

测试方法

package com.yyq;
import com.yyq.schema.Waiter;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class SchemaTest {
    @Test
    public void schemaTest(){
        String configPath = "com\\yyq\\schema\\beans.xml";
        ApplicationContext ctx = new ClassPathXmlApplicationContext(configPath);
        Waiter naiveWaiter = (Waiter)ctx.getBean("naiveWaiter");
        Waiter naughtyWaiter = (Waiter)ctx.getBean("naughtyWaiter");
        naiveWaiter.greetTo("John");
        naughtyWaiter.greetTo("Tom");
    }
}

8.7.2 配置命名切点

<aop:config proxy-target-class="true">
    <aop:aspect ref="adviceMethods">

        <!--定义切点,该切点的命名为greetToPointcut-->
        <aop:pointcut id="greetToPointcut" expression="target(com.yyq.schema.NaiveWaiter) and execution(* greetTo(..))"/>

        <!--引用切点-->
        <aop:before method="preGreeting" pointcut-ref="greetToPointcut"/>
    </aop:aspect>
</aop:config>

<!--//以下定义表示让一个切点能被所有增强访问-->
<aop:config proxy-target-class="true">

    <aop:pointcut id="greetToPointcut2" expression="target(com.yyq.schema.NaiveWaiter) and execution(* greetTo(..))"/>

    <aop:aspect ref="adviceMethods">
        <aop:before method="preGreeting" pointcut-ref="greetToPointcut2"/>
    </aop:aspect>

    <aop:aspect ref="adviceMethods">
        <aop:after method="postGreeting" pointcut-ref="greetToPointcut2"/>
    </aop:aspect>

</aop:config>

注意:如果在 aop:config 元素下直接定义 aop:pointcut ,则必须保证 aop:pointcutaop:aspect 之前定义。aop:config 元素下还能定义 aop:advisor 。三者的配置顺序是:1.首先是aop:pointcut。2.然后是aop:advisor。3.最后是aop:aspect

8.7.3 各种增强类型配置

1.后置增强

<aop:config proxy-target-class="true">
    <aop:aspect ref="adviceMethods">
        <aop:after-returning method="afterReturning"
                             pointcut="target(com.baobaotao.SmartSeller)" returning="retVal" />
    </aop:aspect>
</aop:config>

2.环绕增强

<aop:config proxy-target-class="true">
    <aop:aspect ref="adviceMethods">
        <aop:around method="aroundMethod"
                    pointcut="execution(* serveTo(..)) and within(com.baobaotao.Waiter)" />
    </aop:aspect>
</aop:config>

3.抛出异常增强

<aop:config proxy-target-class="true">
    <aop:aspect ref="adviceMethods">
        <aop:after-throwing method="afterThrowingMethod"
                            pointcut="target(com.baobaotao.SmartSeller) and execution(* checkBill(..))"
                            throwing="iae" />
    </aop:aspect>
</aop:config>

4.Final增强

<aop:config proxy-target-class="true">
    <aop:aspect ref="adviceMethods">
        <aop:after method="afterMethod"
                   pointcut="execution(* com..*.Waiter.greetTo(..))" />
    </aop:aspect>
</aop:config>

5.引介增强

<aop:config proxy-target-class="true">
    <aop:aspect ref="adviceMethods">
        <aop:declare-parents
                implement-interface="com.baobaotao.Seller"
                default-impl="com.baobaotao.SmartSeller"
                types-matching="com.baobaotao.Waiter+" />
    </aop:aspect>
</aop:config>

8.7.4 绑定连接点信息

基于Schema配置的增强方法绑定连接点信息和基于@AspectJ绑定连接点信息所使用的方法没区别。

1.所有增强类型对应的方法第一个入参都可以声明为 JoinPoint(环绕增强声明为:ProceedingJoinPoint)访问连接点信息。

2.<aop:after-returning> (后置增强)可以通过returning属性绑定连接点方法返回值,<aop:after-throwing>(抛出异常增强)可以通过throwing属性绑定连接点方法所抛出的异常。

3.所有增强类型都可以通过可绑定参数的切点函数绑定连接点方法的入参。

1.绑定连接点参数到增强方法

<aop:config proxy-target-class="true">
    <aop:aspect ref="adviceMethods">
        <aop:before method="bindParams"
                    pointcut="target(com.yyq.schema.NaiveWaiter) and args(name,num,..)"/>
    </aop:aspect>
</aop:config>

8.7.5 Advisor配置

Advisor是增强和增强的复合体,是切面。在Schema配置中可以通过aop:advisor配置一个Advisor。通过 advice-ref 属性引用基于接口定义的增强,通过 pointcut 定义切点表达式,或者通过 pointcut-ref 引用一个命名的切点。

<aop:config proxy-target-class="true">
    <aop:advisor advice-ref="testAdvice"  pointcut="execution(* com..*.Waiter.greetTo(..))"/>
</aop:config>
<bean id="testAdvice" class="com.yyq.schema.TestBeforeAdvice"/>

增强类

package com.yyq.schema;
import java.lang.reflect.Method;
import org.springframework.aop.MethodBeforeAdvice;
public class TestBeforeAdvice implements MethodBeforeAdvice {
    public void before(Method method, Object[] args, Object target)
            throws Throwable {
        System.out.println("------TestBeforeAdvice------");
        System.out.println("clientName:"+args[0]);
        System.out.println("------TestBeforeAdvice------");
    }
}

8.8 混合切面类型

Spring虽然使用四种定义切面的方式,但其底层的实现技术都一样。

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:p="http://www.springframework.org/schema/p" xmlns:aop="http://www.springframework.org/schema/aop"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context"
    xsi:schemaLocation="http://www.springframework.org/schema/beans 
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/aop
        http://www.springframework.org/schema/aop/spring-aop.xsd
        http://www.springframework.org/schema/context 
        http://www.springframework.org/schema/context/spring-context.xsd">

    <!-- 方式一 :使用Advisor API方式实现的流程控制切面 -->

    <!--Advisor API 流程切点 指定流程切点的类 和 流程切点的方法 -->
    <bean id="controlFlowPointcut" class="org.springframework.aop.support.ControlFlowPointcut">
        <constructor-arg type="java.lang.Class"
            value="com.xgj.aop.spring.advisor.ControlFlowAdvisor.WaiterDelegate" />
        <constructor-arg type="java.lang.String" value="service" />
    </bean>

    <!--Advisor API 切面 -->
    <bean id="controlFlowAdvisor" class="org.springframework.aop.support.DefaultPointcutAdvisor"
        p:pointcut-ref="controlFlowPointcut" p:advice-ref="greetingBeforeAdvice" />


    <!-- 方式二: 使用@AspectJ注解方式定义切面 扫描 -->
    <aop:aspectj-autoproxy proxy-target-class="true" />


    <aop:config proxy-target-class="true">
        <!-- 命名切点 -->
        <aop:pointcut expression="execution(* com..*.Waiter.greetTo(..))"
            id="beforeAdvice" />
        <!-- 方式三: 基于<aop:advisor>的方式 -->
        <aop:advisor advice-ref="greetingBeforeAdvice"
            pointcut-ref="beforeAdvice" />
    </aop:config>
    <bean id="greetingBeforeAdvice" class="com.xgj.aop.spring.advisor.schema.advisor.GreetingBeforeAdvice" />


    <aop:config proxy-target-class="true">
        <aop:pointcut id="bussinessBindParamProgram"
            expression="target(com.xgj.aop.spring.advisor.schema.bindParameter.BussinessBindParam) and args(name,num,..)" />
        <!-- 方式四:基于<aop:aspect>的方式 -->
        <aop:aspect ref="adviceMethodsBindParam">
            <aop:before pointcut-ref="bussinessBindParamProgram"
                method="crossCutting" />
        </aop:aspect>
    </aop:config>

</beans>

本文标题:SPRING AOP 续二

文章作者:Bangjin-Hu

发布时间:2019年10月15日 - 09:22:26

最后更新:2020年03月30日 - 08:18:02

原始链接:http://bangjinhu.github.io/undefined/第3章 Spring - AOP 续二/

许可协议: 署名-非商业性使用-禁止演绎 4.0 国际 转载请保留原文链接及作者。

Bangjin-Hu wechat
欢迎扫码关注微信公众号,订阅我的微信公众号.
坚持原创技术分享,您的支持是我创作的动力.